module render.rendertocanvas;

import canvas.abstractcanvas;
import utils.vector2;
import utils.color;
import utils.mt19937ar;
import utils.time;
import std.math;
import render.abstractrenderer;
import render.abstractsampler;

import std.stdio;

alias bool delegate(int curLine, int numLines, int pixelRes) RenderProgress;

class RenderToCanvas {
    public RenderProgress onProgress;
    private AbstractRenderer mRenderer;
    private Vector2f mP1, mP2;
    private AbstractSampler mAntialias;
    private Vector2f mPixelSizeInv, mSrcPos;
    private ColorMappingMode mColorMappingMode = ColorMappingMode.none;
    public int mPixelRes;

    public alias mPixelRes pixelRes;

    public this() {
        resetScreenRegion();
        pixelRes = 1;
    }

    public void render(AbstractCanvas canvas) {
        init_genrand(357859234);

        Vector2i res = canvas.size;
        Vector2i renderSize = Vector2i(res.x>canvas.width?canvas.width:res.x,
            res.y>canvas.height?canvas.height:res.y);

        //care about seitenverhaeltnis
        float screenF = cast(float)renderSize.x / cast(float)renderSize.y;

        mSrcPos = mRenderer.renderSize(screenF).mulEntries(mP1);
        Vector2f srcSize = mRenderer.renderSize(screenF).mulEntries(mP2-mP1);

        float srcF = srcSize.x / srcSize.y;

        /*debug writefln("screen: %d %d => %f", renderSize.x, renderSize.y,
            screenF);
        debug writefln("src: %f %f => %f", srcSize.x, srcSize.y, srcF);*/

        if (screenF > srcF) {
            //screen wider than source image => reduce width of screen
            renderSize.x = cast(int)(cast(float)renderSize.y * srcF);
        } else {
            renderSize.y = cast(int)(cast(float)renderSize.x / srcF);
        }

        Vector2f pixelSize = Vector2f.fromInt(renderSize) / srcSize;
        if (mPixelRes > 1)
            pixelSize = pixelSize/cast(float)mPixelRes;
        mPixelSizeInv = 1.0f/pixelSize;

        mRenderer.initRender(pixelSize, mSrcPos, mSrcPos + srcSize);

        //center rendered image
        Vector2i renderPos = canvas.size/2 - renderSize/2;

        //ouch!
        if (renderPos.x < 0) renderPos.x = 0;
        if (renderPos.y < 0) renderPos.y = 0;
        if (renderPos.x+renderSize.x > canvas.width)
            renderSize.x = canvas.width - renderPos.x;
        if (renderPos.y+renderSize.y > canvas.height)
            renderSize.y = canvas.height - renderPos.y;

        assert(renderPos.x+renderSize.x <= canvas.width);
        assert(renderPos.y+renderSize.y <= canvas.height);

        RGBColor col;

        if (mAntialias) {
            mAntialias.renderFunc = &renderFunc;
            mAntialias.initSampler();
        }

        Vector2f pos = Vector2f(0,0);

        if (mPixelRes <= 1) {
            for (int y = 0; y < renderSize.y; y++) {
                if (onProgress)
                    if (!onProgress(y,renderSize.y,1))
                        break;
                for (int x = 0; x < renderSize.x; x++) {
                    pos.x = x;
                    pos.y = y;
                    if (mAntialias)
                        mAntialias.sample(pos,col);
                    else
                        col = renderFunc(pos);
                    col.map(mColorMappingMode);
                    canvas.drawPixel(Vector2i(x+renderPos.x,y+renderPos.y),col);
                }
            }
        } else {
            Vector2i ipos;
            //Pixelblocks of size mPixelRes
            for (int y = 0; y <= (renderSize.y-1)/mPixelRes; y++) {
                if (onProgress)
                    if (!onProgress(y,renderSize.y,mPixelRes))
                        break;
                ipos.y = y*mPixelRes;
                for (int x = 0; x <= (renderSize.x-1)/mPixelRes; x++) {
                    pos.x = x;
                    pos.y = y;
                    col = renderFunc(pos);
                    col.map(mColorMappingMode);
                    //the pixel block in the same scanline
                    for (int z = 0; z < mPixelRes && x*mPixelRes+z < renderSize.x; z++) {
                        ipos.x = x*mPixelRes+z;
                        canvas.drawPixel(ipos, col);
                    }
                }
                //copy the scanline => square sized pixel blocks
                for (int z = 0; z < mPixelRes-1 && ipos.y+z+1 < renderSize.y; z++) {
                    canvas.copyLine(ipos.y, ipos.y+z+1);
                }
            }
        }

        RGBColor backcol = RGBColor(0, 0, 0);

        //clear borders...
        //up and bottom
        canvas.fillRect(Vector2i(0, 0), Vector2i(canvas.width, renderPos.y),
            backcol);
        canvas.fillRect(Vector2i(0, renderPos.y + renderSize.y),
            Vector2i(canvas.width, canvas.height), backcol);
        //left and right
        canvas.fillRect(Vector2i(0, renderPos.y),
            Vector2i(renderPos.x, renderPos.y + renderSize.y), backcol);
        canvas.fillRect(Vector2i(renderPos.x + renderSize.x, renderPos.y),
            Vector2i(canvas.width, renderPos.y + renderSize.y), backcol);

        mRenderer.terminateRender();
    }

    private RGBColor renderFunc(Vector2f pos) {
        RGBColor ret;
        ret = mRenderer.render(pos.mulEntries(mPixelSizeInv) + mSrcPos);
        return ret;
    }

    public void colorMappingMode(ColorMappingMode m) {
        switch (m) {
            case ColorMappingMode.rgbCut:
                writefln("Color mapping: RGB cutoff");
                break;
            case ColorMappingMode.rgbNonlinear:
                writefln("Color mapping: Nonlinear RGB channel limit");
                break;
            case ColorMappingMode.lumaNonlinear:
                writefln("Color mapping: Nonlinear luminance mapping");
                break;
            default:
                writefln("Color mapping: None");
                break;
        }
        mColorMappingMode = m;
    }
    public ColorMappingMode colorMappingMode() {
        return mColorMappingMode;
    }

    ///set a rectangle, the points select a region in the screen (1.0f, 1.0f)
    public void setScreenRegion(Vector2f p1, Vector2f p2, bool add = true) {
        if (p1.x > p2.x) {
            float tmp = p1.x;
            p1.x = p2.x;
            p2.x = tmp;
        }
        if (p1.y > p2.y) {
            float tmp = p1.y;
            p1.y = p2.y;
            p2.y = tmp;
        }

        if (!add) {
            mP1 = p1; mP2 = p2;
        } else {
            //subregion of selection
            auto t1 = mP1 + (mP2-mP1).mulEntries(p1);
            auto t2 = mP1 + (mP2-mP1).mulEntries(p2);
            mP1 = t1;
            mP2 = t2;
        }
        debug writefln("zoom: %s %s", mP1, mP2);
    }

    public void resetScreenRegion() {
        setScreenRegion(Vector2f(0, 0), Vector2f(1, 1), false);
    }

    public void renderer(AbstractRenderer renderer) {
        resetScreenRegion();
        mRenderer = renderer;
    }
    public AbstractRenderer renderer() {
        return mRenderer;
    }

    public void antialias(AbstractSampler sampler) {
        mAntialias = sampler;
        if (mAntialias) {
            mAntialias.renderFunc = &renderFunc;
            mAntialias.initSampler();
        }
    }

    public AbstractSampler antialias() {
        return mAntialias;
    }
}
